package ga.selectionMethods;

import ga.GARun;
import ga.individuals.Individual;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.TreeMap;

public class Universal implements SelectionReplacementMethod {
	private Integer numberOfElements;
	
	public Universal(Integer numberOfElements) throws IllegalArgumentException {
		if (numberOfElements < 0 || numberOfElements > GARun.popSize) {
			throw new IllegalArgumentException("Invalid number of elements!");
		}
		this.numberOfElements = numberOfElements;
	}
	
	private TreeMap<Double, Individual> getAcumFitness (Collection<Individual> individuals) {
		Double totalFitness = 0.0, acum = 0.0, relativeFitness = 0.0;
		TreeMap<Double, Individual> acumFitness = new TreeMap<Double, Individual>();
		
		/* Primera iteracion por cada individuo inevitable para conocer la suma de aptitudes */
		for (Individual individual: individuals) {
			totalFitness += individual.getFitnessValue();
		}
		
		/* Segunda iteracion, calculo de las aptitudes relativas y finalmente las acumuladas */
		for (Individual individual : individuals) {
			relativeFitness = getRelativeFitness(individual, totalFitness);
			acumFitness.put(relativeFitness + acum, individual);
			acum += relativeFitness;
		}
		
		return acumFitness;
	}
	
	private Double getRelativeFitness(Individual individual, Double totalFitness) {
		return	individual.getFitnessValue() / totalFitness;
	}
	
	private Double genNextUniversalRnd(double baseRnd, int j) {
		return (baseRnd + (double)j - 1.0) / (double)this.numberOfElements;
	}

	@Override
	public Collection<Individual> method(Collection<Individual> individuals) {
		TreeMap<Double, Individual> acumFitness = getAcumFitness(individuals);
		List<Individual> selection = new ArrayList<Individual>();
		Double universalRnd, r_j;
		
		/* Genera el random universal (base) */
		universalRnd = Math.random();
		
		/* Elige numberOfElements individuos basado en la aptitud acumulada de cada individuo
		*  y r_j = (r + j - 1) / 3
		*/
		for (int i = 0 ; i < this.numberOfElements ; i++) {
			r_j = genNextUniversalRnd(universalRnd, i+1);
			selection.add(acumFitness.higherEntry(r_j).getValue());
		}
		
		return selection;
	}

}
